#include <vector>
#include <utility>
#include <stdlib.h>
#include "pipe_config.h"
#include "markupstl.h"
#include "include/pipe.h"
#include <assert.h>

namespace anet {
	namespace pipe {
		// ============================================
		// xml config description name.
		static const char* gConfig = "pipe_config";
		static const char* gNode = "node";
		static const char* gServiceDiscovery = "service_discovery";
		static const char* gConnectRelation = "connection_relation";
		static const char* gRelation = "relation";
		static const char* gListenIP = "listenip";
		static const char* gListenPort = "listenport";
		static const char* gName = "name";
		static const char* gId = "id";
		static const char* gAddress = "address";
		static const char* gIndex = "index";
		static const char* gPassword = "password";
		static const char* gKey = "key";
		static const char* gConnect = "connect";
		// ============================================

		CPipeConfig::CPipeConfig() {
			m_relations.clear();
		}

		CPipeConfig::~CPipeConfig() {
			m_relations.clear();
		}

		bool CPipeConfig::Load(const char* filePath) {
			CMarkupSTL markup;
			if (!markup.Load(filePath)) {
				gLog->Crit("load %s file error:%s", filePath, markup.GetError());
				return false;
			}

			// pipe_config node.
			if (!markup.FindElem(gConfig) || !markup.IntoElem()) {
				gLog->Crit("can not find %s error", gConfig);
				return false;
			}

			// node node
			{
				if (!markup.FindElem(gNode)) {
					gLog->Crit("can not find %s node error", gNode);
					return false;
				}

				m_nodeInfo.name = markup.GetAttrib(gName);
				try {
					m_nodeInfo.id = convertToUint32(markup.GetAttrib(gId).c_str());
				} catch (...) {
					return false;
				}
				m_nodeInfo.listenIP = markup.GetAttrib(gListenIP);
				m_nodeInfo.listenPort = std::atoi(markup.GetAttrib(gListenPort).c_str());
			}


			// service_discovery node
			{
				if (!markup.FindElem(gServiceDiscovery)) {
					gLog->Crit("can not find %s node", gServiceDiscovery);
					return false;
				}

				auto addr = markup.GetAttrib(gAddress);
				std::vector<std::string> vec;
				strSplits(addr, ":", vec);
				if (vec.size() != 2) {
					gLog->Crit("service_discovery address: %s error", addr);
					return false;
				}

				m_serviceDiscoveryInfo.ip = vec[0];
				m_serviceDiscoveryInfo.port = std::atoi(vec[1].c_str());
				m_serviceDiscoveryInfo.index = std::atoi(markup.GetAttrib(gIndex).c_str());
				m_serviceDiscoveryInfo.password = markup.GetAttrib(gPassword);
				m_serviceDiscoveryInfo.key = markup.GetAttrib(gKey);
			}

			// connection_relation node.
			{
				if (!markup.FindElem(gConnectRelation) || !markup.IntoElem()) {
					gLog->Crit("can not find %s node", gConnectRelation);
					return false;
				}

				// find all node's connection relation.
				while (markup.FindElem(gRelation)) {
					auto nodeName = markup.GetAttrib(gNode);
					auto connectList = markup.GetAttrib(gConnect);
					auto key = markup.GetAttrib(gKey);
					if (tryToFind(nodeName)) {
						gLog->Crit("find %s node repeated", nodeName);
						return false;
					}

					SRelationInfo &relation = m_relations[nodeName];
					std::vector<std::string> vec;
					strSplits(connectList, ",", relation.connections);	
					relation.key = key;
				}
			}

			return checkConfig();
		}

		bool CPipeConfig::checkConfig() const {
			// my node's relation check.
			if (!tryToFind(this->GetServerName())) {
				return false;
			}

			// relation check.
			for (const auto& eu : m_relations) {
				const auto& key = eu.first;
				const auto& val = eu.second;
				for (size_t i = 0; i < val.connections.size();i++) {
					const auto& connNodeName = val.connections[i];
					const auto* relation = this->findWith(connNodeName);
					assert(relation != nullptr && "can not find node name error");
					if (relation == nullptr) {
						gLog->Crit("can not find %s node's connection", connNodeName);
						return false;
					}

					if (&val != relation) { // not the same value.
						for (size_t j = 0; j < relation->connections.size(); j++) {
							if (relation->connections[j] == key) {
								return false;
							}
						}
					}
				}
			}
			return true;
		}

		bool CPipeConfig::tryToFind(const std::string& nodeName) const {
			return m_relations.find(nodeName) != m_relations.end();
		}
		const nodeRelationInfo* CPipeConfig::findWith(const std::string& nodeName) const {
			auto it = m_relations.find(nodeName);
			if (it == m_relations.end()) {
				return nullptr;
			} else {
				return &it->second;
			}
		}

		uint32 CPipeConfig::GetServerId() const {
			return m_nodeInfo.id;
		}

		const std::string& CPipeConfig::GetServerName() const {
			return m_nodeInfo.name;
		}

		std::string CPipeConfig::GetListenAddr() const {
			return m_nodeInfo.listenIP + ":" + std::to_string(m_nodeInfo.listenPort);
		}

		const serviceDiscoveryDataType& CPipeConfig::GetServiceDiscoveryInfo() const {
			return m_serviceDiscoveryInfo;
		}

		const std::string& CPipeConfig::GetListenIP() const {
			return m_nodeInfo.listenIP;
		}
		uint16 CPipeConfig::GetListenPort() const {
			return m_nodeInfo.listenPort;
		}

		bool CPipeConfig::IsConnectTo(const std::string& nodeName, uint32 nodeId) const {
			auto it = m_relations.find(this->GetServerName());
			if (it == m_relations.end()) {
				return false;
			}

			for (size_t i = 0; i < it->second.connections.size(); i++) {
				if (it->second.connections[i] == nodeName) {
					if (nodeName == this->GetServerName()) {
						// If it is the same name, just compare the id, where big id connects little one.
						return nodeId > this->GetServerId();
					} else {
						return true;
					}
				}
			}
			return false;
		}

		const std::string& CPipeConfig::GetNodeToken(const std::string& nodeName) const {
			auto it = m_relations.find(nodeName);
			if (it == m_relations.end()) {
				return "";
			}
			return it->second.key;
		}
	}
}